/*
 *
 */
package org.jiscinvolve.astrodabis.barberry.hibernate.core;

import java.util.Date;
import java.util.List;
import java.util.Iterator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.Serializable;

import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import javax.persistence.FetchType;
import javax.persistence.Enumerated;
import javax.persistence.JoinColumn;
import javax.persistence.GenerationType;
import javax.persistence.GeneratedValue;

import javax.persistence.EnumType;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import org.hibernate.annotations.Index;
import org.hibernate.annotations.NamedQuery;
import org.hibernate.annotations.NamedQueries;
import org.hibernate.annotations.GenericGenerator;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.Primary;

import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; 

import org.jiscinvolve.astrodabis.barberry.common.core.Job;
import org.jiscinvolve.astrodabis.barberry.common.core.Tag;
import org.jiscinvolve.astrodabis.barberry.common.core.Womble;
import org.jiscinvolve.astrodabis.barberry.common.core.Account;
import org.jiscinvolve.astrodabis.barberry.common.core.Identifier;
import org.jiscinvolve.astrodabis.barberry.common.core.SimpleIdent;
import org.jiscinvolve.astrodabis.barberry.common.core.AbstractIdent;
import org.jiscinvolve.astrodabis.barberry.common.core.exception.*;

import org.jiscinvolve.astrodabis.barberry.common.data.RowTag;
import org.jiscinvolve.astrodabis.barberry.common.data.JoinTag;
import org.jiscinvolve.astrodabis.barberry.common.data.TapColumn;

import org.jiscinvolve.astrodabis.barberry.hibernate.data.TapColumnEntity;

import org.jiscinvolve.astrodabis.barberry.hibernate.annotation.*;

/**
 *
 */
@Entity
@Table(
    name = JobEntity.DB_TABLE_NAME
    )
@NamedQueries(
        {
        @NamedQuery(
            name  = "job-all",
            query = "FROM JobEntity ORDER BY ident desc"
            ),
        @NamedQuery(
            name  = "job-owner",
            query = "FROM JobEntity WHERE owner = :owner ORDER BY ident desc"
            ),
        @NamedQuery(
            name  = "job-recent-all",
            query = "FROM JobEntity ORDER BY ident desc"
            ),
        @NamedQuery(
            name  = "job-recent-owner",
            query = "FROM JobEntity WHERE owner = :owner ORDER BY ident desc"
            ),
        @NamedQuery(
            name  = "job-tagged-row",
            query = "SELECT DISTINCT rowtag.job FROM RowTagEntity as rowtag WHERE rowtag.tag = :tag ORDER BY rowtag.job desc"
            ),
        @NamedQuery(
            name  = "job-tagged-join",
            query = "SELECT DISTINCT jointag.job FROM JoinTagEntity as jointag WHERE jointag.tag = :tag ORDER BY jointag.job desc"
            )
        }
    )
public class JobEntity
extends OwnedEntity
implements Job
    {

    /**
     * Our debug logger.
     * 
     */
    protected static Log log = LogFactory.getLog(JobEntity.class);

    public static final String DB_TABLE_NAME = "jobs"  ;
    public static final String DB_IDENT_COL  = "ident" ;
    public static final String DB_NAME_COL   = "name"  ;
    public static final String DB_TEXT_COL   = "text"  ;

    public static final String DB_ANNOT_TYPE = "annottype" ;

    public static final String DB_COLUMN_ONE = "colone";
    public static final String DB_COLUMN_TWO = "coltwo";

    public static final String DB_COLNAME_ONE = "aliasone";
    public static final String DB_COLNAME_TWO = "aliastwo";

    public static final String DB_NAME_SRC = "namesrc";
    public static final String DB_DATA_SRC = "datasrc";

    public static final String DB_TAG_NAME = "tagname";
    public static final String DB_TAG_DATA = "tagdata";

    public static final String DB_SOURCE_SRC = "sourcesrc";
    public static final String DB_SOURCE_URL = "sourceurl";

    public static final String DB_JOB_STATUS   = "status";
    public static final String DB_JOB_COUNT    = "count";
    public static final String DB_JOB_STARTED  = "started";
    public static final String DB_JOB_FINISHED = "finished";

    @Repository
    public static class Factory
    extends AbstractFactory<Job, JobEntity>
    implements Job.Factory
        {

        @Override
        @UpdateObject
        public long batch(Job job, long limit, Iterator iter)
        throws ProcessingException
            {
// Check status is PROCESSING.
            try {

                long count = 0 ;

                //
                // Start our stateless session.
                try {
                    startless();
                    stateless().begin();
                    while((count < limit) && (iter.hasNext()))
                        {
                        iter.next();
                        count++;
                        }
                    stateless().commit();
                    }
                catch (ProcessingException ouch)
                    {
                    stateless().rollback();
                    throw ouch ;
                    }
                catch (Throwable ouch)
                    {
                    stateless().rollback();
                    throw ouch ;
                    }
                finally
                    {
                    stateless().done();
                    }

                if (iter.hasNext())
                    {
                    job.status(
                        Status.PROCESSING
                        );
                    }
                else {
                    job.status(
                        Status.COMPLETED
                        );
                    }
                job.count(
                    count
                    );
                pushQuick(
                    job
                    );

                return count ;
                }
            catch (ProcessingException ouch)
                {
                job.fail(ouch);
                throw ouch ;
                }
            catch (Throwable ouch)
                {
                job.fail(ouch);
                log.warn("Unexpected Exception while processing job [" + job.ident() + "][" + ouch.getMessage() + "][" + ouch.getClass() + "]");
ouch.printStackTrace();
                throw new ProcessingException(
                    ouch
                    );
                }
/*
            finally
                {
                hibernate().clear();
                }
 */
            }

        @Override
        @UpdateObject
        public void push(Job job)
            {
            pushQuick(
                job
                );
            }

        @Override
        @UpdateQuick
        public void pushQuick(Job job)
            {
            hibernate().update(
                job
                );
            }

        @Override
        public Identifier ident(String ident)
            {
            try {
                return new SimpleIdent<Long>(
                    Long.valueOf(
                        ident
                        )
                    );
                }
            catch (NumberFormatException ouch)
                {
                throw new IdentFormatException(
                    ouch
                    );
                }
            }

        @Override
        @CreateObject
        public Job create(Account owner, Job.Bean bean)
            {
            return createQuick(
                owner,
                bean
                );
            }

        @Override
        @CreateQuick
        public Job createQuick(Account owner, Job.Bean bean)
            {
            return hibernate().insert(
                new JobEntity(
                    owner,
                    bean
                    )
                );
            }

        @Override
        @SelectObject
        public Job select(Identifier ident)
            {
            return hibernate().select(
                JobEntity.class,
                ident
                );
            }

        @Override
        @SelectObject
        public Iterable<Job> select()
            {
            return hibernate().iterable(
                hibernate().query(
                    "job-all"
                    )
                );
            }

        @Override
        @SelectObject
        public Iterable<Job> select(Account owner)
            {
            return hibernate().iterable(
                hibernate().query(
                    "job-owner"
                    ).setEntity(
                        "owner",
                        owner
                        )
                );
            }

        @Override
        @SelectObject
        public Iterable<Job> recent(int limit)
            {
            return hibernate().iterable(
                hibernate().query(
                    "job-recent-all"
                    ).setMaxResults(
                        limit
                        )
                );
            }

        @Override
        @SelectObject
        public Iterable<Job> recent(Account owner, int limit)
            {
            return hibernate().iterable(
                hibernate().query(
                    "job-recent-owner"
                    ).setEntity(
                        "owner",
                        owner
                    ).setMaxResults(
                        limit
                        )
                );
            }

        @Override
        @SelectObject
        public Iterable<Job> jointags(Tag tag)
            {
            return hibernate().iterable(
                hibernate().query(
                    "job-tagged-join"
                    ).setEntity(
                        "tag",
                        tag
                        )
                );
            }

        @Override
        @SelectObject
        public Iterable<Job> rowtags(Tag tag)
            {
            return hibernate().iterable(
                hibernate().query(
                    "job-tagged-row"
                    ).setEntity(
                        "tag",
                        tag
                        )
                );
            }


        @Override
        @UpdateObject
        public void update(Job job)
            {
            hibernate().update(
                job
                );
            }

        @Override
        @UpdateObject
        public void delete(Job job)
            {
            //
            // Delete the associated rowtags.
            Womble.womble().rowtags().delete(
                job
                );
            //
            // Delete the associated jointags.
            Womble.womble().jointags().delete(
                job
                );
            //
            // Delete the Job.
            hibernate().delete(
                job
                );
            }
        }

    protected JobEntity()
        {
        super();
        }

    protected JobEntity(Account owner, Job.Bean bean)
        {
        super(
            owner
            );

        this.name = bean.getName();
        this.text = bean.getText();

        this.annotType = bean.getAnnotType();

        this.columnOne = bean.getColumnOne();
        this.columnTwo = bean.getColumnTwo();

        this.colNameOne = bean.getColNameOne();
        this.colNameTwo = bean.getColNameTwo();

        this.tagNameSrc = bean.getTagNameSrc();
        this.tagDataSrc = bean.getTagDataSrc();

        this.tagName = bean.getTagName();
        this.tagData = bean.getTagData();

        this.sourceType = bean.getSourceType();
        this.sourceUrl  = bean.getSourceUrl();

        if ((this.colNameOne == null) && (this.columnOne != null))
            {
            this.colNameOne = this.columnOne.name();
            }
        if ((this.colNameTwo == null) && (this.columnTwo != null))
            {
            this.colNameTwo = this.columnTwo.name();
            }

        }

    @Id
    @Column(
        name = DB_IDENT_COL
        )
    @GeneratedValue(
        generator="worker-hilo"
        )
    @GenericGenerator(
        name="worker-hilo",
        strategy="hilo"
        )
    private Long ident ;

    @Override
    public Identifier ident()
        {
        return new AbstractIdent()
            {
            @Override
            public Serializable value()
                {
                return ident ;
                }
            };
        }

    @Column(
        name = DB_NAME_COL
        )
    private String name ;

    @Override
    public String name()
        {
        if ((this.name != null) && (this.name.trim().length() > 0))
            {
            return this.name ;
            }
        else {
            return this.ident.toString();
            }
        }

    @Column(
        name = DB_TEXT_COL
        )
    private String text ;

    @Override
    public String text()
        {
        return this.text ;
        }

    @Column(
        name = DB_ANNOT_TYPE
        )
    @Enumerated(
        EnumType.STRING
        )
    private AnnotType annotType=AnnotType.SINGLE ;
    public AnnotType annotType()
        {
        return this.annotType;
        }

    @ManyToOne(
        fetch = FetchType.LAZY,
        targetEntity = TapColumnEntity.class
        )
    @JoinColumn(
        name = DB_COLUMN_ONE,
        unique = false,
        nullable = true,
        updatable = false
        )
    private TapColumn columnOne ;

    @Override
    public TapColumn columnOne()
        {
        return this.columnOne ;
        }

    @ManyToOne(
        fetch = FetchType.LAZY,
        targetEntity = TapColumnEntity.class
        )
    @JoinColumn(
        name = DB_COLUMN_TWO,
        unique = false,
        nullable = true,
        updatable = false
        )
    private TapColumn columnTwo ;

    @Override
    public TapColumn columnTwo()
        {
        return this.columnTwo ;
        }

    @Column(
        name = DB_COLNAME_ONE
        )
    private String colNameOne; 

    @Override
    public String colNameOne()
        {
        return this.colNameOne;
        } 

    @Column(
        name = DB_COLNAME_TWO
        )
    private String colNameTwo;

    @Override
    public String colNameTwo()
        {
        return this.colNameTwo;
        } 

    @Column(
        name = DB_NAME_SRC
        )
    @Enumerated(
        EnumType.STRING
        )
    private TagNameSrc tagNameSrc;

    @Override
    public TagNameSrc tagNameSrc()
        {
        return this.tagNameSrc;
        }

    @Column(
        name = DB_DATA_SRC
        )
    @Enumerated(
        EnumType.STRING
        )
    private TagDataSrc tagDataSrc;

    @Override
    public TagDataSrc tagDataSrc()
        {
        return this.tagDataSrc;
        }

    @Column(
        name = DB_TAG_NAME
        )
    private String tagName;

    @Override
    public String tagName()
        {
        return this.tagName;
        }

    @Column(
        name = DB_TAG_DATA
        )
    private Double tagData;

    @Override
    public Double tagData()
        {
        return this.tagData;
        }

    @Column(
        name = DB_SOURCE_SRC
        )
    private SourceType sourceType;

    @Override
    public SourceType sourceType()
        {
        return this.sourceType;
        }

    @Column(
        name = DB_SOURCE_URL
        )
    private String sourceUrl;

    @Override
    public String sourceUrl()
        {
        return this.sourceUrl;
        }

    @Column(
        name = DB_JOB_STATUS
        )
    @Enumerated(
        EnumType.STRING
        )
    private Status status = Status.EDITING ;

    @Override
    public Status status()
        {
        return this.status ;
        }

    @Override
    public void status(Status value)
        {
        this.status = value ;
        switch(value)
            {
            case PROCESSING :
                if (this.started == null)
                    {
                    this.started = new Date();
                    }
                break ;

            case COMPLETED :
            case CANCELLED :
            case FAILED :
                if (this.started == null)
                    {
                    this.started = new Date();
                    }
                if (this.finished == null)
                    {
                    this.finished = new Date();
                    }
                break ;

            default :
                break ;
            }
        }

    @Column(
        name = DB_JOB_COUNT
        )
    private long count ;

    @Override
    public long count()
        {
        return this.count ;
        }

    @Override
    public void count(long count)
        {
        this.count += count ;
        }

    @Column(
        name = DB_JOB_STARTED,
        nullable = true
        )
    @Temporal(
        TemporalType.TIMESTAMP
        )
    private Date started ;

    @Override
    public Date started()
        {
        return this.started ;
        }

    @Column(
        name = DB_JOB_FINISHED,
        nullable = true
        )
    @Temporal(
        TemporalType.TIMESTAMP
        )
    private Date finished ;

    @Override
    public Date finished()
        {
        return this.finished ;
        }

    @Override
    public boolean equals(Object that)
        {
        if (this == that)
            {
            return true ;
            }
        if (this.ident == null)
            {
            return false ;
            }
        if (that instanceof JobEntity)
            {
            return this.ident.equals(
                ((JobEntity)that).ident
                );
            }
        if (that instanceof Job)
            {
            return this.ident().equals(
                ((Job)that).ident()
                );
            }
        return false ;
        }

    @Override
    public String toString()
        {
        StringBuilder builder = new StringBuilder();
        builder.append(
            "Job["
            );
        builder.append(
            this.ident
            );
        builder.append(
            "]{ "
            );
        builder.append(
            this.owner().toString()
            );
        builder.append(
            ", name{"
            );
        builder.append(
            this.name()
            );
        builder.append(
            "}}"
            );
        return builder.toString();
        }

    @Override
    public void update(Bean bean)
        {
        this.name = bean.getName();
        this.text = bean.getText();
        Womble.womble().jobs().push(
            this
            );        
        }

    @Override
    public void fail(Throwable cause)
        {
        this.status = Status.FAILED ;
        try {
            Womble.womble().jobs().push(
                this
                );        
            }
        catch (Throwable ouch)
            {
            log.error("Exception while logging job failure [" + this.ident + "][" + ouch.getMessage() + "][" + ouch.getClass() + "]");
            }
        }

    @Override
    public Tags tags()
        {
        return new Tags()
            {
            public Iterable<Tag> select()
                {
                return Womble.womble().tags().select(
                    JobEntity.this
                    );
                }
            };
        }

    @Override
    public RowTags rowtags()
        {
        return new RowTags()
            {
            public Iterable<RowTag> select()
                {
                return Womble.womble().rowtags().select(
                    JobEntity.this
                    );
                }

            public Iterable<RowTag> select(int limit)
                {
                return Womble.womble().rowtags().select(
                    JobEntity.this,
                    limit
                    );
                }
            };
        }

    public JoinTags jointags()
        {
        return new  JoinTags()
            {
            public Iterable<JoinTag> select()
                {
                return Womble.womble().jointags().select(
                    JobEntity.this
                    );
                }

            public Iterable<JoinTag> select(int limit)
                {
                return Womble.womble().jointags().select(
                    JobEntity.this,
                    limit
                    );
                }
            };
        }

    @Override
    public void delete()
        {
        log.debug("JobEntity.delete()");
        Womble.womble().jobs().delete(
            this
            );
        }

    }

